import { ComponentFixture, fakeAsync, tick, TestBed, waitForAsync } from '@angular/core/testing';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, TemplateRef, ViewChild } from '@angular/core';
import { NotificationModule } from './notification.module';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { ButtonModule } from '../button/button.module';
import { NotificationService } from './notification.service';

@Component({
  template: `
    <button id="create" lv-button (click)="create()">Create</button>
    <button id="destroy" lv-button (click)="destroy()">Destroy</button>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
class TestNotificationComponent {
  @Input() lvNotficationKey: string = '';
  @Input() lvTitle: string = 'title';
  @Input() lvIcon: string = 'icon';
  @Input() lvContent: string | TemplateRef<void> = 'content';
  @Input() lvWidth: number = 360;
  @Input() lvDuration: number = 0;
  @Input() lvKeepShowOnHover: boolean = true;
  @Input() lvPosition: 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight' = 'topRight';
  @Input() lvOffset: { x: number; y: number } = { x: 0, y: 0 };
  @Input() lvOnDestroy: () => void | {};

  constructor(private notification: NotificationService) {}

  create(): void {
    this.notification.create({
      lvNotificationKey: this.lvNotficationKey,
      lvTitle: this.lvTitle,
      lvIcon: this.lvIcon,
      lvContent: this.lvContent,
      lvWidth: this.lvWidth,
      lvDuration: this.lvDuration,
      lvKeepShowOnHover: this.lvKeepShowOnHover,
      lvPosition: this.lvPosition,
      lvOffset: this.lvOffset,
      lvOnDestroy: this.lvOnDestroy,
    });
  }

  destroy(id?: string): void {
    this.notification.destroy(id);
  }

  onDestroy = (): void => {};
}

describe('NotificationComponent', () => {
  beforeEach(waitForAsync(() => {
    TestBed.configureTestingModule({
      imports: [ButtonModule, NotificationModule, NoopAnimationsModule],
      declarations: [TestNotificationComponent],
      teardown: { destroyAfterEach: true },
    }).compileComponents;
  }));

  let component: TestNotificationComponent;
  let fixture: ComponentFixture<TestNotificationComponent>;
  let cdr: ChangeDetectorRef;

  const create = () => {
    const button = fixture.nativeElement.querySelector('#create');
    button.dispatchEvent(new Event('click'));
  };
  const destory = () => {
    const button = fixture.nativeElement.querySelector('#destory');
    button.dispatchEvent(new Event('click'));
  };

  const getNotificationBox = () => document.querySelector('lv-notification');

  const clearDomNodes = () => {
    document.body.innerHTML = '';
  };

  describe('render', () => {
    const detectChange = fakeAsync(() => {
      tick(500);
      fixture.detectChanges();
    });

    const openNotification = params => {
      for (const [key, value] of Object.entries(params)) {
        component[key] = value;
      }
      fixture.detectChanges();
      create();
      detectChange();
      expect(getNotificationBox()).toMatchSnapshot();
    };
    beforeEach(() => {
      fixture = TestBed.createComponent(TestNotificationComponent);
      component = fixture.componentInstance;
      component.lvTitle = 'Notification';
      component.lvContent = 'This is a notice';
    });

    afterEach(() => {
      clearDomNodes;
    });

    it('with default', () => {
      openNotification({});
    });

    it('with lvIcon', () => {
      openNotification({ lvIcon: 'lv-icon-status-info' });
    });

    it('with lvWidth', () => {
      openNotification({ lvWidth: 200 });
    });

    it('with lvOffset', () => {
      openNotification({ lvOffset: { x: 20, y: -20 } });
    });
  });

  describe('works correctly', () => {
    const detectChanges = () => {
      tick(1000);
      fixture.detectChanges();
    };

    beforeEach(() => {
      fixture = TestBed.createComponent(TestNotificationComponent);
      component = fixture.componentInstance;
      cdr = fixture.debugElement.injector.get(ChangeDetectorRef);
      fixture.autoDetectChanges();
      component.lvTitle = 'Notification';
      component.lvContent = 'This is a notice';
    });

    afterEach(() => {
      clearDomNodes;
    });

    it('with lvNotificationKey', fakeAsync(() => {
      component.lvNotficationKey = 'custom-notification';
      fixture.detectChanges();
      create();
      detectChanges();
      expect(getNotificationBox()).toBeTruthy();

      create();
      detectChanges();
      expect(document.querySelectorAll('lv-notification')[1]).toBeFalsy();
    }));

    it('with destory', fakeAsync(() => {
      jest.spyOn(component, 'onDestroy');
      component.lvOnDestroy = component.onDestroy;
      fixture.detectChanges();
      create();
      detectChanges();
      expect(getNotificationBox()).toBeTruthy();

      component.destroy();
      detectChanges();
      expect(getNotificationBox()).toBeFalsy();
      expect(component.onDestroy).toBeCalledTimes(1);
    }));

    it('with close button', fakeAsync(() => {
      create();
      detectChanges();

      const closeButton = document.querySelector('.lv-notification-close-button > i');
      closeButton.dispatchEvent(new Event('click'));
      detectChanges();
      expect(getNotificationBox()).toBeFalsy();
    }));

    it('with lvDuration', fakeAsync(() => {
      component.lvDuration = 2000;
      create();
      tick(500);
      fixture.detectChanges();
      expect(getNotificationBox()).toBeTruthy();

      tick(1500);
      cdr.detectChanges();
      expect(getNotificationBox()).toBeNull();
    }));

    it('with lvKeepShowOnHover', fakeAsync(() => {
      component.lvDuration = 2000;
      create();
      tick(500);
      fixture.detectChanges();
      expect(getNotificationBox()).toBeTruthy();

      const content = document.querySelector('.lv-notification');
      content.dispatchEvent(new Event('mouseenter'));

      tick(1500);
      cdr.detectChanges();
      expect(getNotificationBox()).toBeTruthy();

      content.dispatchEvent(new Event('mouseleave'));
      tick(1500);
      cdr.detectChanges();
      tick(1500);
      cdr.detectChanges();
      expect(getNotificationBox()).toBeNull();
    }));
  });
});
